//---------------------------------------------------------------------------------------------------------
//
//	KeyDownBrowser
//
//	Inherits From:		NXBrowser
//
//	Declared In:		KeyDownBrowser.h
//
//	Disclaimer
//
//		You may freely copy, distribute and reuse this software and its
//		associated documentation. I disclaim any warranty of any kind, 
//		expressed or implied, as to its fitness for any particular use.
//
//---------------------------------------------------------------------------------------------------------
#import "KeyDownBrowser.h"
#import "KeyDownMatrix.h"


#define RETURN		13
#define LEFTARROW	172
#define UPARROW	173
#define RIGHTARROW	174
#define DOWNARROW	175
#define DELETE	127

#define CHUNK 		64


@implementation KeyDownBrowser

//---------------------------------------------------------------------------------------------------------
//	 Timed Entry Methods (Private)
//---------------------------------------------------------------------------------------------------------
void Search (timedEntry, now, self)
	DPSTimedEntry	timedEntry;
	double			now;
	void*			self;
{
	[(id)self perform: @selector (_search)];
}


- _addTimedEntry
{
    	timedEntry = DPSAddTimedEntry (inputTimeOut, Search, (void*) self, 		NX_MODALRESPTHRESHOLD);
    	NXPing();
    	return self;
}


- _removeTimedEntry
{
	if ( ! timedEntry) return self;
	DPSRemoveTimedEntry (timedEntry);
	timedEntry = 0;
	return self;
}


//---------------------------------------------------------------------------------------------------------
//	 Miscellaneous Methods (Private)
//---------------------------------------------------------------------------------------------------------
- _chunk
{
	//  Allocate memory for input buffer.
	
	if ( ! searchString)
		searchString = NX_MALLOC (searchString, char, CHUNK);
	else
		searchString = NX_REALLOC (searchString, char, strlen(searchString) + CHUNK);
		
	if ( ! searchString) NXLogError ("Out of Memory in _chunk!");
	return self;
}


- _selectCellInRow:(int)aRow ofMatrix:aMatrix andDoClick:(BOOL)doClick
{
	[aMatrix selectCellAt: aRow :0];
	[aMatrix scrollCellToVisible: aRow :0];
	if (doClick) [self doClick: aMatrix];
	return self;
}


//---------------------------------------------------------------------------------------------------------
//	 Search Methods (Private)
//---------------------------------------------------------------------------------------------------------
- (int) _searchColumn
{
	//  If there is no selection within the browser, return column 0.  If there are multiple selections
	//  return the current selected column.  If there is 1 selection, then if it is a leaf, return the 
	//  current selected column.  If it is a branch, return the current selected column + 1.
	
	id	selectedCellList;
	int	searchColumn = [self selectedColumn];
	
	if (searchColumn < 0) return 0;

	if ([self isMultipleSelectionEnabled])
		{
		selectedCellList = [self getSelectedCells: nil];
		if ([selectedCellList count] == 1) 
			if ([[selectedCellList objectAt: 0] isLeaf] == NO)
				searchColumn++;
		selectedCellList = [selectedCellList free];
		return searchColumn;
		}
		
	if ([self selectedCell])
		if ([[self selectedCell] isLeaf] == NO) searchColumn++;
		
	return searchColumn;
}


- _search
{
	//  Searches current search column for first occurrence of searchString.
	
	int		iterator;
	int		searchColumn = [self _searchColumn];
	id		matrix = [self matrixInColumn: searchColumn];
	id		currentCell;
	BOOL	found = NO;
	
	[self _removeTimedEntry];

	for (iterator = 0; iterator < [matrix numRows]; iterator++)
		{
		currentCell = [matrix cellAt: iterator :0];
		if (NXOrderStrings([currentCell stringValue], searchString, 
			wantsCaseSensitiveSearch, strlen (searchString), NULL) == 0)
			{
			[matrix selectCellAt: -1 :-1];
			[self _selectCellInRow:iterator ofMatrix:matrix andDoClick:YES];
			found = YES;
			break;
			}
		}
	
	if (! found) NXBeep();
	memset (searchString, 0, strlen (searchString));
	return self;
}


//---------------------------------------------------------------------------------------------------------
//	 Key Down Methods (Private)
//---------------------------------------------------------------------------------------------------------
- _upArrow: (NXEvent *) theEvent
{
	id	matrix;
	int	selectedRow;
	int	selectedColumn = [self selectedColumn];
	
	if (selectedColumn == -1) return self;
	matrix = [self matrixInColumn:selectedColumn];
	selectedRow = [matrix selectedRow] - 1;
	if (selectedRow < 0) selectedRow = 0;
	[self _selectCellInRow:selectedRow ofMatrix:matrix andDoClick:YES];
	return self;
}


- _downArrow: (NXEvent *) theEvent
{
	id	matrix;
	int	selectedRow;
	int	selectedColumn = [self selectedColumn];
	
	if (selectedColumn == -1) return self;
	matrix = [self matrixInColumn: selectedColumn];
	selectedRow = [matrix selectedRow] + 1;
	if (selectedRow > [matrix numRows] - 1) selectedRow = [matrix numRows] - 1;
	[self _selectCellInRow:selectedRow ofMatrix:matrix andDoClick:YES];
	return self;
}


- _leftArrow: (NXEvent *) theEvent
{
	id	matrix;
	int	selectedColumn = [self selectedColumn];
	
	if (selectedColumn == -1) return self;
	matrix = [self matrixInColumn: selectedColumn];
	[self setLastColumn: selectedColumn];
	[matrix selectCellAt: -1 :-1];
	[matrix sendAction:action to:target];
	if (selectedColumn == 0) return self;
	selectedColumn -= 1;
	matrix = [self matrixInColumn: selectedColumn];
	if ([self firstVisibleColumn] > selectedColumn) [self scrollColumnsRightBy: 1];
	[matrix scrollCellToVisible: [matrix selectedRow] :[matrix selectedCol]];
	return self;
}


- _rightArrow: (NXEvent *) theEvent
{
	id matrix = [self matrixInColumn: ([self selectedColumn] + 1)];
	if (! matrix) return self;
	[self _selectCellInRow:0 ofMatrix:matrix andDoClick:YES];
	return self;
}


- _return: (NXEvent *) theEvent
{
	int	selectedColumn = [self selectedColumn];
	
	if (selectedColumn == -1) return self;
	[self doDoubleClick: [self matrixInColumn:selectedColumn]];
	return self;
}


//---------------------------------------------------------------------------------------------------------
//	 Initialization
//---------------------------------------------------------------------------------------------------------
- initFrame: (const NXRect *) frameRect
{
	//  Give it default IB characteristics, set the Matrix class, enable keys.
	
	[super initFrame: frameRect];
	[self setMatrixClass: [KeyDownMatrix class]];
	[self separateColumns: YES];
	[self setHorizontalScrollerEnabled: YES];
	[self setEmptySelectionEnabled: YES];
	[self setTitle: "KeyDownBrowser" ofColumn: 0];
	[self acceptArrowKeys:YES andSendActionMessages:YES];
	[self wantsCaseSensitiveSearch: NO];
	[self inputTimeOut: 0.5];
	[self _chunk];
	memset (searchString, 0, CHUNK);
	return self;
}


- free
{
	[self _removeTimedEntry];
	if (searchString) free (searchString);
	return self = [super free];
}


//---------------------------------------------------------------------------------------------------------
//	 Accessors
//---------------------------------------------------------------------------------------------------------
- wantsCaseSensitiveSearch: (BOOL) aFlag
{
	wantsCaseSensitiveSearch = aFlag;
	return self;
}


- (BOOL) wantsCaseSensitiveSearch
{
	return wantsCaseSensitiveSearch;
}


- inputTimeOut: (float) aFloat
{
	inputTimeOut = aFloat;
	return self;
}


- (float) inputTimeOut
{
	return inputTimeOut;
}


//---------------------------------------------------------------------------------------------------------
//	 Event Handling
//---------------------------------------------------------------------------------------------------------
- mouseDown: (NXEvent*) theEvent
{
	if ([self isEnabled] == NO) return self;
	[super mouseDown: theEvent];
	[[self window] makeFirstResponder: self] ;
	return self;
}


- keyDown: (NXEvent *) theEvent
{
	if ([self isEnabled] == NO) return self;
		
	[self _removeTimedEntry];

	switch (theEvent->data.key.charCode)
		{
		case UPARROW:
			[self _upArrow: theEvent];
			break;
		case DOWNARROW:
			[self _downArrow: theEvent];
			break;
		case LEFTARROW:
			[self _leftArrow: theEvent];
			break;
		case RIGHTARROW:
			[self _rightArrow: theEvent];
			break;
		case RETURN:
			[self _return: theEvent];
			break;
		case DELETE:
			if ([[self delegate] respondsTo:@selector(delete:)])
				[[self delegate] delete:self];
			break;
		default:
			if (theEvent->data.key.repeat) return self;
			sprintf (searchString, "%s%c", searchString, theEvent->data.key.charCode);
			if (! (strlen (searchString) % (CHUNK - 1))) [self _chunk];
			[self _addTimedEntry];
			break;
		}
		
	return self;
}


- (BOOL)acceptsFirstResponder
{
	return YES;
}


//----------------------------------------------------------------------------------------------------
//	Archiving Methods
//----------------------------------------------------------------------------------------------------
- read: (NXTypedStream*) stream
{
	[super read: stream];
	NXReadTypes (stream, "cf", &wantsCaseSensitiveSearch, &inputTimeOut);
	return self;
}


- write: (NXTypedStream*) stream
{
	[super write: stream];
	NXWriteTypes (stream, "cf", &wantsCaseSensitiveSearch, &inputTimeOut);
	return self;
}


- awake
{
	[super awake];
	[self _chunk];
	memset (searchString, 0, CHUNK);
	return self;
}


//----------------------------------------------------------------------------------------------------
//	Superclass Overrides
//----------------------------------------------------------------------------------------------------
- acceptArrowKeys:(BOOL)acceptFlag andSendActionMessages:(BOOL)sendFlag
{
	[super acceptArrowKeys:YES andSendActionMessages:sendFlag];
	return self;
}


- acceptArrowKeys:(BOOL)acceptFlag
{
	//  2.1 method.
	[super acceptArrowKeys:YES andSendActionMessages:YES];
	return self;
}


//---------------------------------------------------------------------------------------------------------
//	 IB Methods
//---------------------------------------------------------------------------------------------------------
- (const char*) getInspectorClassName
{
	return "KeyDownBrowserInspector";
}


@end